/*******************************
 * mm.c
 *
 * Source code for the dynamic memory manager
 *
 ******************************/

#include "mm.h"
#include <stdlib.h>
#include <stdio.h>


alloc* HEAD = NULL;
char* allocation = NULL;
int mem_leaks;
int buff_ovflows;
int free_err;
unsigned long pool;

int mm_init(unsigned long size) {
	if((allocation = (char*) malloc(sizeof(char)*size)) == NULL) {
        perror("Initialization failed. Size either 0 or not enough memory");
        return -1;
    }
    mem_leaks = 0;
    buff_ovflows = 0;
    free_err = 0;
    pool = size; //keeps track of pool size to avoid over-allocation
	return 0; 
}

int mm_end() {
    alloc* node = NULL; 
	alloc* tmp = NULL;
    if(HEAD != NULL) { //Goes through non-empty list and frees each node, counting each as a memory leak.
        node = HEAD;
        while(node != NULL) {
            mem_leaks++;
            tmp = node->next;
            delete_node(node->ptr);
            if(tmp != NULL) { //Keeps from accessing a null pointer
                node = tmp;
            }
            else {
                node = NULL;
            }
            
        }
    }
    if(allocation != NULL) {
        free(allocation); //Frees the pool
    }
    printf("Memory Leaks: %d\n", mem_leaks);
    printf("Buffer Overflows: %d\n", buff_ovflows);
    printf("Free Errors: %d\n", free_err);
    return 0;
}

char *mm_alloc(unsigned long no_of_chars) {
    unsigned long searched = 0;
    unsigned long usable = 0;
    unsigned long size = 0;
    char* mem;
    mem = &allocation[0]; //Starts at beginning of pool.
    while(searched < pool) {
        if(usable == no_of_chars) { //Adds node when it finds the right size block
            add_node(mem, no_of_chars);
            return mem;
        }
        size = search(mem + usable); //Searches from current base pointer + the currently found usable blocks
        if(size == -1) { //If nothing is found, keep going until usable == no_of_chars
            ++searched;
            ++usable;
        }
        else { //If an allocated block is found in the way, reset the base pointer to the end of it and reset the usable count.
            ++searched;
            mem = mem + usable + size;
            usable = 0;
        }
    }
    if(searched == pool) {
        printf("Not enough memory in pool\n");
    }
    return NULL;
}

int mm_free(char *ptr) {
    unsigned long size = search(ptr);
    if(size != -1) { //If something is found, reset the values to null and free it.
        int i = 0;
        char *tmp;
        tmp = ptr;
        for(i = 0; i < size; ++i) {
            *tmp = NULL;
            tmp++;
        }
        delete_node(ptr);
        return 0;
    } 
    else { //If nothing is found, memory has not been allocated.
        printf("Memory already free\n");
        free_err++;
        return 0;
    }
    
}

int mm_assign(char *ptr, char val) {
    alloc* tmp = NULL;
    tmp = HEAD;
    int i = 0;
    char* ptmp;
    while(tmp != NULL) { //Looks for an allocated pointer matching the input. If it's not there, counts a buffer overflow.
        i = 0;
        for(i = 0; i < tmp->size; ++i) {
            ptmp = (tmp->ptr) + i;
            if(ptmp == ptr) {
                *ptr = val;
                return 0;
            }
        }
        tmp = tmp->next;
    }
    printf("Memory at pointer not allocated: buffer overflow\n");
    buff_ovflows++;
    return -1;
}

int add_node(char* ptr, unsigned long size){
    
	alloc* node = NULL;
	if (HEAD == NULL){
		/* We are creating the very first node
         Allocate memory to HEAD here and check for any errors 
         */
        HEAD = (alloc*) malloc(sizeof(alloc));
		HEAD->ptr = ptr;
        HEAD->size = size;
        HEAD->next = NULL;
	}
	else{
		/*  Allocate memory for a node here and check for any errors. 
         Move to end to add node.
         */
        node = (alloc*) malloc(sizeof(alloc));
        node->next = HEAD;
        HEAD = node;
        node->ptr = ptr;
        node->size = size;
	}
	return 0;
}



unsigned long search(char* ptr){
    
	alloc* node = HEAD;
	while(node != NULL){
        if(node->ptr == ptr) {
            return node->size;
        }
        else {
            node = node->next;
        }
	}
	//printf("Did not find pointer\n");
	return -1;
}


/*	Delete a node having the specified data */

int delete_node(char* ptr){
    
	alloc* node = NULL; 
	alloc* tmp = NULL;
    
	// If there are no nodes at all, return 0 
	if (HEAD == NULL){
		printf("List is empty\n");
		return 0;
	}
	// If the HEAD has data delete it and return 0
	if (HEAD->ptr == ptr){
        node = HEAD;
		HEAD = HEAD->next;
        free(node);
        return 0;
	}
    
	// Search through the list 
	node = HEAD;
	tmp = HEAD->next;
	while(tmp != NULL){
		if (tmp->ptr == ptr){
			// Appropriately handle the "next" pointer and delete the data 
            node->next = tmp->next;
            free(tmp);
            return 0;
		}
		node = tmp;
		tmp = tmp->next;
	}
	printf("Could not find pointer\n");
	return -1;	
}



